#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# PyWikipediaBot based bot to be used with lyricwiki.org.
# Framework for uploading lyrics to lyricwiki.org.
#
# Copyright (c) 2008, umat
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice, this 
# list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# - The names of the contributors may not be used to endorse or promote products 
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import urllib, re, sys, getopt, os
from sgmllib import SGMLParser
from string import *
import htmlentitydefs, re
import wikipedia

_char = re.compile(r'&(\w+?);')
_dec  = re.compile(r'&#(\d{2,4});')
_hex  = re.compile(r'&#x(\d{2,4});')


def color(t, c):
	return chr(0x1b)+"["+str(c)+"m"+t+chr(0x1b)+"[0m"
def black(t):
	return color(t, 30)
def red(t):
	return color(t, 31)
def green(t):
	return color(t, 32)
def yellow(t):
	return color(t, 33)
def blue(t):
	return color(t, 34)
def mangenta(t):
	return color(t, 35)
def cyan(t):
	return color(t, 36)
def white(t):
	return color(t, 37)
def bold(t):
	return color(t, 1)

def _char_unescape(m, defs=htmlentitydefs.entitydefs):
	try:
		return defs[m.group(1)]
	except KeyError:
		return m.group(0)

def unescape(string):
	"""
	Unescape all HTML sequences like &nbsp;, &uuml; etc.
	"""
	result = _hex.sub(lambda x: unichr(int(x.group(1), 16)),\
		_dec.sub(lambda x: unichr(int(x.group(1))),\
			_char.sub(_char_unescape, string)))
	if string.__class__ != unicode:
		return result.encode('utf-8')
	else:
		return result



class LyricWikiUploader():
	def __init__(self):
		self.dry = True
		self.pagelist = []
		self.language = ""
  
	def get_lyrics_header(self, artist):
		"""
		Get the header for the lyrics page
		"""
		r = u"[[Category:Review Me]]\n{{Song||%s|star=Green}}" % artist
		return r

	def get_lyrics_footer(self, artist, album, song):
		"""
		Get the footer for the lyrics page
		"""
		fLetter = song.upper()[0]
		fLetter = re.sub(r"[0-9]", "0-9", fLetter)
		r = u"{{SongFooter\n|artist   = %s\n|album    = %s\n|song     = %s\n|fLetter  = %s\n|video    = \n|audio    = \n|language = %s\n|asin = \n|iTunes   = \n}}" % (artist, album, song, fLetter, self.language)
		return r

	def format_artist(self, artist):
		"""
		Tries to format the name of the artist according to 
		http://lyricwiki.org/LyricWiki:Page_Names

		For now the same as format_song
		"""
		return self.format_song(artist)


	def format_song(self, song):
		"""
		Tries to format the name of the song according to 
		http://lyricwiki.org/LyricWiki:Page_Names
		"""
		# resolve html encoded characters
		song = unescape(song)

		# handle casing
		song = song.title()
		song = song.replace('`',"'")
		song = song.replace("'S","'s")
		song = song.replace("'T","'t")
		song = song.replace("'L","'l")
		song = song.replace('[','(')
		song = song.replace(']',')')
		song = song.replace('#','Number')
		song = song.replace('<','Less than')
		song = re.sub(r"\(.*?\)", "", song)

		# remove features
		song = re.sub(r"(?i)feat\..*", "", song)
		song = re.sub(r"(?i)ft\..*", "", song)
		song = re.sub(r"(?i)featuring\.*", "", song)
		
		# remove new lines and multiple spaces
		song = re.sub(r"(?si)\n", "", song)
		song = re.sub(r"(?si)<br>", " ", song)
		song = re.sub(r"(?si)\s+", " ", song)

		song = song.strip()
		return song

	def clean_lyrics(self, lyrics):
		"""
		Clean the lyrics from some generic HTML tags and strips
		starting and trailing white-spaces from every line.
		"""
		lyrics = re.sub(r"(?si)<br>", "\n", lyrics)
		lyrics = re.sub(r"(?si)&#8230;", "...", lyrics)
		lyrics = re.sub(r"(?si)<br\s*/?>", "", lyrics)
		lyrics = re.sub(r"(?si)<p\s*/?>", "", lyrics)
		lyrics = re.sub(r"(?si)</p>", "", lyrics)
		lyrics = re.sub(r"(?si)<b>", "'''", lyrics)
		lyrics = re.sub(r"(?si)</b>", "'''", lyrics)
		lyrics = re.sub(r"(?si)<i>", "''", lyrics)
		lyrics = re.sub(r"(?si)</i>", "''", lyrics)
		lyrics = re.sub(r"(?si)<.*?>", "", lyrics)
		lyrics = re.sub(r"(?si)&acute;", "'", lyrics)
		lyrics = re.sub(r"(?si)&quot;", "\"", lyrics)
#		lyrics = unescape(unicode(lyrics, "latin-1"))
		lyrics = unescape(lyrics)

		r = u""
		for s in lyrics.split("\n"):
			r = r + strip(s) + "\n"

		return r

	def upload_lyrics(self, url, songs, forceArtist=None, skipTracks=[]):
		"""
		Main function to upload the lyrics to the artist page.

		Calls the get_song method to get the artist, song and lyrics.
		"""
		try:
			artist, song, lyrics = self.get_song(url)
			if forceArtist:
				artist = forceArtist

			if song in skipTracks:
				print "Skipping %s ..." %song
				return

			title = "%s:%s" % (artist, song)
			text = self.get_lyrics_header(artist)+u"\n<lyrics>\n"+lyrics+u"</lyrics>\n\n"+self.get_lyrics_footer(artist, "", song)
			if self.dry:
				print "Update [[%s]]" % title
				print "Writing %s/%s.txt", (artist, song)
				f = open("%s/%s.txt" % (artist, song),"w")
				f.write(lyrics.encode("utf-8"))
				f.close()
				#print bold(black(text))
				if not title in songs:
					self.pagelist.append("# '''[[%s|%s]]'''" % (title, song))
			else:
				site = wikipedia.getSite()
				page = wikipedia.Page(site, title)
				try:
					## Just skip existing lyrics.
					## TODO: Maybe add an option to force overwrite.
					page.get(get_redirect = True)
					print "Skipped %s" % page
				except wikipedia.NoPage:
					print "Update %s" % page
					page.put(text, comment="Added Lyrics")
					if not title in songs:
						print "Song is not on page!"
						self.pagelist.append("# '''[[%s|%s]]'''" % (title, song))
					else:
						print "Song is already there"

		except Exception, ex:
			print "Failed for %s" % url
			import traceback
			print >>sys.stderr, "Uncaught exception:", str(ex)
			print >>sys.stderr, traceback.format_exc()
		

	def get_existing_songs(self, artist):
		"""
		Get a list of the lyrics linked from the artist page in the form Artist:Song.
		It does not matter if the lyrics exists or not.
		"""
		songs = []
		site = wikipedia.getSite()
		page = wikipedia.Page(site, artist)
		text = page.get(get_redirect = True)
		for line in text.split('\n'):
			m = re.search("(?i)# '''\[\[(.*)\|.*\]\]'''", line)
			if m:
				songs.append(m.group(1))
		return songs	


	def get_lyrics_by_album(self, artist):
		"""
		Gets a list of lyrics associated with each album as a dictionary.
		"""
		songs = {}
		site = wikipedia.getSite()
		page = wikipedia.Page(site, artist)
		text = page.get(get_redirect = True)
		section = "Other Songs" # initially assume "Other Songs"
		songs[section] = []
		for line in text.split('\n'):
			m = re.search("(?i)==(.*)==", line)
			if m:
				sec = m.group(1)
				sec = re.sub("\[\[.*?:(.*?)\|.*\]\]", "\\1",  sec).strip()
				sec = sec.replace("_"," ")
				if not sec.startswith("="):
					section = sec
					songs[section] = []
			else:
				m = re.search("(?i)# '''\[\[(.*)\|.*\]\]'''", line)
				if m:
					songs[section].append(m.group(1))
		return songs	

	def upload_all_lyrics(self, artist, forceArtist=None, skipTracks=[]):
		"""
		Main method to iterate over all available lyrics.

		Optionally allows to skip the given tracks and force an
		alternative artist name.
		"""
		try:
			existing_songs = self.get_existing_songs(artist)
		except:
			## Just silently fail...
			existing_songs = []

		songlist = self.get_song_list(artist)
		cnt = 0
		l = len(songlist)
		for song in songlist:
			cnt += 1
			print "[%d/%d] Processing %s" % (cnt, l, song)
			self.upload_lyrics(song, existing_songs, forceArtist, skipTracks)

		## TODO: Add this to the artist page
		for i in self.pagelist:
			print i


	def clean_other_songs(self, artist):
		"""
		Helper function which tries to reduce the list
		of tracks in "Other Songs" by removing duplicates
		that are already associated to an album.

		Currently just prints the formatted list to stdout for copy and pasting.
		"""
		songs = self.get_lyrics_by_album(artist)
		for k in songs:
			if k == "Other Songs": continue # don't remove itself 
			for s in songs[k]:
				if songs["Other Songs"].count(s) > 0:
					songs["Other Songs"].remove(s)

		for s in songs["Other Songs"]:
			p = re.search(".*:(.*)", s)
			print "# '''[[%s|%s]]'''" % (s,p.group(1))


	def get_song_list(self, artist):
	    raise NotImplementedError("Virtual Method")

	def get_song(self, url):
	    raise NotImplementedError("Virtual Method")

	def usage(self):
		progname = os.path.basename(sys.argv[0])
		print "Usage: %s [OPTION] [Artist]" % bold(progname)
		print "\nUploads lyrics for songs to %s." % bold(blue("http://lyricswiki.org"))
		print "\n  %s, %-20s  force artist [artist]" % ( bold("-f"), bold("--force-artist") )
		print "\n  %s, %-20s  set language [language]" % ( bold("-l"), bold("--language") )
		print "\n  %s, %-20s  dry run" % ( bold("-d"), bold("--dry") )
		print 

	def parse_arguments_and_upload(self):
		try:
		    opts, args = getopt.getopt(sys.argv[1:], "hf:dl:", ["help", "force-artist=", "dry", "language"])
		except getopt.GetoptError, err:
		    print "Error: %s" % red(str(err))
		    self.usage()
		    return 2
		self.dry = False
		forceArtist = None
		self.language = ""
		for o, a in opts:
		    if o == "-d":
			self.dry = True
		    elif o in ("-h", "--help"):
			usage()
			return 0
		    elif o in ("-f", "--force-artist"):
			forceArtist = a
		    elif o in ("-l", "--language"):
			self.language = a
		    else:
			assert False, "unhandled option"

		if len(args) != 1: 
		    self.usage()
		    return 2

		artist = args[0]
		
		self.upload_all_lyrics(artist, forceArtist=forceArtist)
